/*
 *  Copyright 2016 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package org.webrtc;

import android.media.MediaRecorder;

/**
 * Base interface for camera1 and camera2 implementations. Extends VideoCapturer with a
 * switchCamera() function. Also provides subinterfaces for handling camera events, and a helper
 * class for detecting camera freezes.
 */
public interface CameraVideoCapturer extends VideoCapturer {
	/**
	 * Camera events handler - can be used to be notifed about camera events. The callbacks are
	 * executed from an arbitrary thread.
	 */
	public interface CameraEventsHandler {
		// Camera error handler - invoked when camera can not be opened
		// or any camera exception happens on camera thread.
		void onCameraError(String errorDescription);

		// Called when camera is disconnected.
		void onCameraDisconnected();

		// Invoked when camera stops receiving frames.
		void onCameraFreezed(String errorDescription);

		// Callback invoked when camera is opening.
		void onCameraOpening(String cameraName);

		// Callback invoked when first camera frame is available after camera is started.
		void onFirstFrameAvailable();

		// Callback invoked when camera is closed.
		void onCameraClosed();
	}

	/**
	 * Camera switch handler - one of these functions are invoked with the result of switchCamera().
	 * The callback may be called on an arbitrary thread.
	 */
	public interface CameraSwitchHandler {
		// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
		void onCameraSwitchDone(boolean isFrontCamera);

		// Invoked on failure, e.g. camera is stopped or only one camera available.
		void onCameraSwitchError(String errorDescription);
	}

	/**
	 * Switch camera to the next valid camera id. This can only be called while the camera is running.
	 * This function can be called from any thread.
	 */
	void switchCamera(CameraSwitchHandler switchEventsHandler);

	/**
	 * MediaRecorder add/remove handler - one of these functions are invoked with the result of
	 * addMediaRecorderToCamera() or removeMediaRecorderFromCamera calls.
	 * The callback may be called on an arbitrary thread.
	 */
	public interface MediaRecorderHandler {
		// Invoked on success.
		void onMediaRecorderSuccess();

		// Invoked on failure, e.g. camera is stopped or any exception happens.
		void onMediaRecorderError(String errorDescription);
	}

	/**
	 * Add MediaRecorder to camera pipeline. This can only be called while the camera is running.
	 * Once MediaRecorder is added to camera pipeline camera switch is not allowed.
	 * This function can be called from any thread.
	 */
	void addMediaRecorderToCamera(MediaRecorder mediaRecorder, MediaRecorderHandler resultHandler);

	/**
	 * Remove MediaRecorder from camera pipeline. This can only be called while the camera is running.
	 * This function can be called from any thread.
	 */
	void removeMediaRecorderFromCamera(MediaRecorderHandler resultHandler);

	/**
	 * Helper class to log framerate and detect if the camera freezes. It will run periodic callbacks
	 * on the SurfaceTextureHelper thread passed in the ctor, and should only be operated from that
	 * thread.
	 */
	public static class CameraStatistics {
		private final static String TAG = "CameraStatistics";
		private final static int CAMERA_OBSERVER_PERIOD_MS = 2000;
		private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000;

		private final SurfaceTextureHelper surfaceTextureHelper;
		private final CameraEventsHandler eventsHandler;
		private int frameCount;
		private int freezePeriodCount;
		// Camera observer - monitors camera framerate. Observer is executed on camera thread.
		private final Runnable cameraObserver = new Runnable() {
			@Override
			public void run() {
				final int cameraFps = Math.round(frameCount * 1000.0f / CAMERA_OBSERVER_PERIOD_MS);
				Logging.d(TAG, "Camera fps: " + cameraFps + ".");
				if (frameCount == 0) {
					++freezePeriodCount;
					if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount >= CAMERA_FREEZE_REPORT_TIMOUT_MS
							&& eventsHandler != null) {
						Logging.e(TAG, "Camera freezed.");
						if (surfaceTextureHelper.isTextureInUse()) {
							// This can only happen if we are capturing to textures.
							eventsHandler.onCameraFreezed("Camera failure. Client must return video buffers.");
						} else {
							eventsHandler.onCameraFreezed("Camera failure.");
						}
						return;
					}
				} else {
					freezePeriodCount = 0;
				}
				frameCount = 0;
				surfaceTextureHelper.getHandler().postDelayed(this, CAMERA_OBSERVER_PERIOD_MS);
			}
		};

		public CameraStatistics(
				SurfaceTextureHelper surfaceTextureHelper, CameraEventsHandler eventsHandler) {
			if (surfaceTextureHelper == null) {
				throw new IllegalArgumentException("SurfaceTextureHelper is null");
			}
			this.surfaceTextureHelper = surfaceTextureHelper;
			this.eventsHandler = eventsHandler;
			this.frameCount = 0;
			this.freezePeriodCount = 0;
			surfaceTextureHelper.getHandler().postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS);
		}

		private void checkThread() {
			if (Thread.currentThread() != surfaceTextureHelper.getHandler().getLooper().getThread()) {
				throw new IllegalStateException("Wrong thread");
			}
		}

		public void addFrame() {
			checkThread();
			++frameCount;
		}

		public void release() {
			surfaceTextureHelper.getHandler().removeCallbacks(cameraObserver);
		}
	}
}
